﻿using System.Collections.Generic;

namespace LedCubeClient.CommunicationSystem
{
    public class Rfc1055: IProtocol
    {
        private const byte END = 0xc0;   /*0300 indicates end of packet */
        private const byte ESC = 0xdb;   /*0333 indicates byte stuffing */
        private const byte ESC_END = 0xdc; /*0334 ESC ESC_END means END data byte */
        private const byte ESC_ESC = 0xdd; /*0335 ESC ESC_ESC means ESC data byte */
        
        public override void SendPacket(string p, MessageHeader header)
        {
            var bytes = new byte[p.Length];
            for (int i = 0; i < bytes.Length;i++ )
            {
                bytes[i] = (byte)p[i];
            }
            SendPacket(bytes, bytes.Length, header);    
        }

        public override void SendPacket(byte[] p, int len, MessageHeader header )
        {
            if (SendByte == null) return;
            /* send an initial END character to flush out any data that may
            * have accumulated in the receiver due to line noise
            */
            SendByte(END);
            SendByte(header.ToByte());

            
            /* for each byte in the packet, send the appropriate character sequence */
            for (int i = 0; i < len; i++)
            {
                switch (p[i])
                {
                    /* if it’s the same code as an END character, we send a
                    * special two character code so as not to make the
                    * receiver think we sent an END
                    */
                    case END:
                        SendByte(ESC);
                        SendByte(ESC_END);
                        break;

                    /* if it’s the same code as an ESC character,
                    * we send a special two character code so as not
                    * to make the receiver think we sent an ESC
                    */
                    case ESC:
                        SendByte(ESC);
                        SendByte(ESC_ESC);
                        break;

                    /* otherwise, we just send the character
                */
                    default:
                        SendByte(p[i]);
                        break;
                }
            }
        }


        /* RECV_PACKET: receives a packet into the buffer located at "p".
        *      If more than len bytes are received, the packet will
        *      be truncated.
        *      Returns the number of bytes stored in the buffer.
        */
        public byte[] RecvPacket(int len)
        {
            if (ReadByte == null) return null;

            var p = new List<byte>();
            byte c;
            int received = 0;

            /* sit in a loop reading bytes until we put together
             * a whole packet.
             * Make sure not to copy them into the packet if we
             * run out of room.
             */
            while (true)
            {
                /* get a character to process*/
                c = ReadByte();

                /* handle bytestuffing if necessary*/
                switch (c)
                {
                    /* if it's an END character then we're done with the packet */
                    case END:
                        /* a minor optimization: if there is no data in the packet, ignore it. This is
                        * meant to avoid bothering IP with all the empty packets generated by the
                        * duplicate END characters which are in turn sent to try to detect line noise.
                        */
                        if (p.Count > 0) return p.ToArray();
                        break;

                    /* if it's the same code as an ESC character, wait
                    * and get another character and then figure out
                    * what to store in the packet based on that.
                    */
                    case ESC:
                        c = ReadByte();
                        /* if "c" is not one of these two, then we have a protocol violation.  
                         * The best bet seems to be to leave the byte alone and 
                         * just stuff it into the packet */
                        switch (c)
                        {
                            case ESC_END:
                                c = END;
                                break;
                            case ESC_ESC:
                                c = ESC;
                                break;
                        }
                        break;
                    /* here we fall into the default handler and let it store the character for us */
                    default:
                        if (received < len)
                            p[received++] = c;
                        break;
                }
            }
        }

    }
}
